﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Lisp.Compiler
{
    public partial class Processor
    {
        private Stack<double> _data = new Stack<double>();

        private int _stackPosition = 0;
        private int _inputPosition = 0;
        private int _outputPosition = 0;
        private int _maxStackCount = 0;

        public double[] Input { get; set; }
        public double[] Output { get; set; }
        public Instruction<Processor>[] Code { get; set; }

        public Processor(int maxStackCount)
        {
            if (maxStackCount < 0)
            {
                throw new ArgumentException("invalid data stack size");
            }
            _maxStackCount = maxStackCount;
        }
        public void Initialize()
        {
            if (_stackPosition != 0)
            {
                throw new ArgumentException("invalid instructions count");
            }
            if (Code == null)
            {
                throw new ArgumentException("invalid instructions count");
            }
            if (Input == null)
            {
                throw new ArgumentException("invalid input instructions");
            }
            if (Output == null)
            {
                throw new ArgumentException("invalid output instructions");
            }
            int executableCount = Count<IExecutableInstruction<Processor>>();
            if (executableCount == 0)
            {
                throw new ArgumentException("invalid executable instructions");
            }
            int inputCount = Count<IReadInstruction<double, Processor>>();
            if (inputCount == 0)
            {
                throw new ArgumentException("invalid input instructions");
            }
            int outputCount = Count<IWriteInstruction<double, Processor>>();
            if (outputCount == 0)
            {
                throw new ArgumentException("invalid output instructions");
            }
            if (Code.Length == 0 || Code.Length != (executableCount + inputCount + outputCount))
            {
                throw new ArgumentException("invalid instructions count");
            }
            if (Input.Length == 0 || Input.Length != inputCount)
            {
                throw new ArgumentException("invalid input instructions");
            }
            if (Output.Length == 0 || Output.Length != outputCount)
            {
                throw new ArgumentException("invalid output instructions");
            }
            _inputPosition = 0;
            _outputPosition = 0;
            _data.Clear();
        }
        public void Process()
        {
            int i = 0;
            int instructionsCount = Code.Length;
            while (i < instructionsCount)
            {
                Instruction<Processor> instruction = Code[i++];
                IReadInstruction<double, Processor> n1 = instruction as IReadInstruction<double, Processor>;
                if (n1 != null)
                {
                    n1.Push(this, Input[_inputPosition++]);
                    continue;
                }
                IWriteInstruction<double, Processor> n2 = instruction as IWriteInstruction<double, Processor>;
                if (n2 != null)
                {
                    Output[_outputPosition++] = n2.Pop(this);
                    continue;
                }
                IExecutableInstruction<Processor> n3 = (IExecutableInstruction<Processor>)instruction;
                n3.Execute(this);
            }
        }

        #region private methods

        private int Count<T>()
            where T : class, IEquatable<Processor>
        {
            int counter = 0;
            int instructionsCount = Code.Length;
            while (instructionsCount-- > 0)
            {
                T n = Code[instructionsCount] as T;
                if (n != null && n.Equals(this))
                {
                    counter++;
                }
            }
            return counter;
        }
        private void Push(double value)
        {
            _data.Push(value);
        }
        private double Pop()
        {
            return _data.Pop();
        }
        #endregion

        #region static methods
        public static Instruction<Processor> Push(Processor code)
        {
            if (code._stackPosition == code._maxStackCount)
            {
                throw new ArgumentException("stack oveflow");
            }
            code._stackPosition++;
            Push pop = new Push(code);
            return pop;
        }
        public static Instruction<Processor> Pop(Processor code)
        {
            if (code._stackPosition < 1)
            {
                throw new ArgumentException("stack underflow");
            }
            code._stackPosition--;
            Pop pop = new Pop(code);
            return pop;
        }
        public static Instruction<Processor> Add(Processor code)
        {
            if (code._stackPosition < 2)
            {
                throw new ArgumentException("stack underflow");
            }
            code._stackPosition--;
            Add add = new Add(code);
            return add;
        }
        #endregion
    }
}
